package eval.engine.FireFighting;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import eval.deploy.IDemandManager;
import eval.engine.IPlacement;
import eval.engine.cplex.CplexMapIndexToVmId;
import eval.engine.cplex.InitialCplexCode.CplexDataHolder;
import eval.host.Host;
import eval.host.IHostManager;
import eval.vm.VMIdentifier;
import eval.vm.VirtualMachine;

/**
 * This class is the data structure used to collect all the data gathered in each cycle of the FIRE_FIGHTING engine.
 * In each cycle, a new instance of this class is created, filled and saved.
 * At the end of the experiment, this data is used to plot the experiment results.
 * 
 * @author Amit Peled   <amit.kapina@gmail.com>
 * @author Igal Kaufman <igal.kaufman@gmail.com> 
 */
public class FireFightingPlacement implements IPlacement {

	private Map<Host, List<VirtualMachine>> vmMapping = new HashMap<Host, List<VirtualMachine>>();
	private Map<Integer,List<VMIdentifier> > CloudMap = new HashMap<Integer,List<VMIdentifier> >();
	private Map<VirtualMachine, Integer> CPUDemandOfVMs;
	private Map<VirtualMachine, Integer> MEMDemandOfVMs; 

	private List<VirtualMachine> vmsWithSatisfiedDemand = new ArrayList<VirtualMachine>();
	private List<VirtualMachine> vmsWithUNSatisfiedDemand = new ArrayList<VirtualMachine>();
	private List<VirtualMachine> VMSrequestedTobedeployed;
	private List<VirtualMachine> RejectedVMS;
	private List<VirtualMachine> PlacedVMS;

	private List<Host> turendONHosts = new ArrayList<Host>();
	private List<Host> turendOFFHosts = new ArrayList<Host>();
	private List<Integer> OpenHosts = new ArrayList<Integer>();
	private int[] HostLoadAtPlacement;	

	private int tick;
	private IPlacement lastPlacement; 
	private IDemandManager demandManager;
	private IHostManager hostManager;
	private List<VirtualMachine> removedList;
	private int numberOfVMSinCloud = 0;
	private boolean emptyPlacement = false;
	private int totalCloudCPULoad = 0;
	private int totalCloudCpuDemand = 0;
	private int totalCloudMemDemand = 0;
	private double minCpuLoadInHosts = Double.POSITIVE_INFINITY;
	private double maxCpuLoadInHosts = Double.NEGATIVE_INFINITY;
	private double minMemLoadInHosts = Double.POSITIVE_INFINITY;
	private double maxMemLoadInHosts = Double.NEGATIVE_INFINITY;
	private int totalSatisfiedDemand = 0;
	private int totalUNSatisfiedDemand = 0;
	private int numberOfOpenHosts = 0;
	private int numberOfCloseHosts = 0;
	private int numberOfCandidateHosts = 0;
	private int vmRelocate = 0;
	private int totalCpuCapcityAtOpenHosts = 0;
	private int totalCpuCapcityAtCloud = 0;
	private int totalMemCapcityAtOpenHosts=0;
	private int totalCloudMemAtCloud = 0;
	private int baseHostCost = 0;
	private double hostsStartUpCost = 0.0;
	private int totalRequestedCPU = 0;
	private double sumOfMinREservtion = 0.0;
	private int gradient;
	private double[] freeCpuAtHosts;

	/**
	 * @Constructor
	 * @param vmMapping_
	 * @param hostManager_
	 * @param lastPlacement_
	 * @param demandManager_
	 * @param removedList_
	 * @param vmsToDeploy_
	 * @param PlacedVMS_
	 * @param RejectedVMS_
	 * @param tick_
	 * @param vmRelocate_
	 * @param gradient
	 */
	public FireFightingPlacement(Map<Host, List<VirtualMachine>> vmMapping_, IHostManager hostManager_,
			IPlacement lastPlacement_, IDemandManager demandManager_  , List<VirtualMachine> removedList_
			,List<VirtualMachine> vmsToDeploy_,List<VirtualMachine> PlacedVMS_ ,List<VirtualMachine> RejectedVMS_
			,int tick_, List<VirtualMachine> vmRelocate_, int gradient) {

		this.lastPlacement = lastPlacement_;
		this.vmMapping = vmMapping_;
		this.hostManager = hostManager_;
		this.removedList = removedList_;
		this.VMSrequestedTobedeployed = vmsToDeploy_;
		this.PlacedVMS = PlacedVMS_;
		this.RejectedVMS = RejectedVMS_;
		this.demandManager = demandManager_;
		if(VMSrequestedTobedeployed.size() != (PlacedVMS.size() +RejectedVMS.size()))
			System.out.println("Error at tick: "+tick);
		this.tick = tick_;
		this.gradient = gradient;
		this.CPUDemandOfVMs = demandManager.getCpuDemandOfAllVMsAtTick(tick);
		this.MEMDemandOfVMs = demandManager.getMemDemandOfAllVMsAtTick(tick);
		HostLoadAtPlacement = new int[hostManager.getNumberOfHosts()];
		this.vmRelocate = vmRelocate_.size();
		updateCloudMap(vmMapping_);
		updatePlacement();

	}

	/*
	 * 
	 */
	private void setTurendONHosts() {
		if(tick == 0) return;
		for(Host curHost : 	vmMapping.keySet() ){
			if(curHost.isOpen() || curHost.isCandidateForClosing() ){
				OpenHosts.add(curHost.getHostId());
				if(!lastPlacement.getOpenHosts().contains(curHost.getHostId())){
					turendONHosts.add(curHost);
				}
			}
		}
	}

	/*
	 * 
	 */
	private void setTurendOFFHosts() {
		if(tick == 0) return;
		for(Host curHost : 	vmMapping.keySet() ){
			if(lastPlacement.getOpenHosts().contains(curHost.getHostId()) && (!this.getOpenHosts().contains(curHost.getHostId()))){
				turendOFFHosts.add(curHost);
			}
		}
	}

	/*
	 */
	private void updateCloudMap(Map<Host, List<VirtualMachine>> vmMAP){
		for(Host host : vmMAP.keySet()){
			CloudMap.put(host.getHostId(), new ArrayList<VMIdentifier>());

			for(VirtualMachine vm : vmMAP.get(host)){
				CloudMap.get(host.getHostId()).add(vm.getIdentifier());
			}
		}

	}

	/*
	 * This is the main fun. we run on the map end update the data Structure 
	 */
	private void updatePlacement(){
		double sumOfMEMinHost = 0;
		// iterates over hosts
		for (Host host : vmMapping.keySet()) {
			hostManager.updateCurrentHostDemand(demandManager, host, vmMapping.get(host), tick);
			sumOfMEMinHost = 0;
			HostLoadAtPlacement[host.getHostId()] = host.getCurrentCpuLoad();
			if(host.isOpen()){
				numberOfOpenHosts++;}
			else if(host.isClose()){
				numberOfCloseHosts++;}
			else{numberOfCandidateHosts++;}

			if(host.getCurrentCpuLoad() < minCpuLoadInHosts){
				minCpuLoadInHosts = host.getCurrentCpuLoad();
			}
			if(host.getCurrentCpuLoad() > maxCpuLoadInHosts){
				maxCpuLoadInHosts = host.getCurrentCpuLoad();
			}

			// iterates over vms within the host
			for (VirtualMachine vm : vmMapping.get(host)) {
				sumOfMinREservtion+= vm.getVMConfiguration().getMinCpu();
				numberOfVMSinCloud++;

				totalCloudCPULoad += vm.getSatisfiedCpuDemand();

				totalCloudCpuDemand +=  CPUDemandOfVMs.get(vm);//  demandManager.getCPUDemandOfVmAtTick(vm,tick);

				totalCloudMemDemand += MEMDemandOfVMs.get(vm);

				sumOfMEMinHost += MEMDemandOfVMs.get(vm);

				int CurVmCons = vm.getSatisfiedCpuDemand();
				if(CurVmCons == CPUDemandOfVMs.get(vm) ||
						CurVmCons < vm.getVMConfiguration().getMinCpu()){
					vmsWithSatisfiedDemand.add(vm);
					totalSatisfiedDemand += CurVmCons;
					int unSatCpuChk = CPUDemandOfVMs.get(vm).intValue()-vm.getSatisfiedCpuDemand();
					if(unSatCpuChk>0)
						totalUNSatisfiedDemand += unSatCpuChk;
				}
				else{
					vmsWithUNSatisfiedDemand.add(vm);
					totalUNSatisfiedDemand += CPUDemandOfVMs.get(vm) - CurVmCons;
					int unSatCpuChk = vm.getSatisfiedCpuDemand();
					if(unSatCpuChk>0)
						totalSatisfiedDemand += unSatCpuChk;
				}


			}//end: iterates over vms within the host

			if(sumOfMEMinHost < minMemLoadInHosts){
				minMemLoadInHosts = sumOfMEMinHost;
			}
			if(sumOfMEMinHost > maxMemLoadInHosts){
				maxMemLoadInHosts = sumOfMEMinHost;
			}

		}//end: iterates over hosts

		setTurendONHosts();
		setTurendOFFHosts();
		List<Host> hosts =  hostManager.getOpenHosts();
		for(Host it : hosts) {
			totalCpuCapcityAtOpenHosts += it.getHostCapacity().getCpuCapacity();
			totalMemCapcityAtOpenHosts += it.getHostCapacity().getMemCapacity();
			baseHostCost += hostManager.getBaseCostForHost(it.getHostCapacityEnum());
		}
		hosts =  hostManager.getHosts();
		for(Host it : hosts) {
			totalCpuCapcityAtCloud += it.getHostCapacity().getCpuCapacity();
			totalCloudMemAtCloud += it.getHostCapacity().getMemCapacity(); 
		}
		hosts =  getTurnedONHosts();
		for(Host it : hosts) {
			hostsStartUpCost += hostManager.getStartUpCostForHost(it.getHostCapacityEnum());
		}
		//the Requested CPU include the cpu demand of vms that have been rejected,so we must add them to the calculation.
		if(!RejectedVMS.isEmpty()){
			for(VirtualMachine vm : RejectedVMS){
				totalRequestedCPU += demandManager.getCPUDemandOfVmAtTick(vm, tick);
			}
		}
		totalRequestedCPU += totalCloudCpuDemand;

		freeCpuAtHosts = new double[hostManager.getNumberOfHosts()];
		for(int l = 0;l<hostManager.getNumberOfHosts();l++) {
			Host host  =   hostManager.getHostByID(l);
			if(host.isClose())
				freeCpuAtHosts[l] = 0 ; 
			else
				freeCpuAtHosts[l] = 
				((double)host.getHostCapacity().getCpuCapacity()-(double)host.getCurrentCpuLoad())
				/(double)host.getHostCapacity().getCpuCapacity();
		}
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getTotalRequestedCPU()
	 */
	@Override
	public int getTotalRequestedCPU() {
		return totalRequestedCPU ;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getOpenHosts()
	 */
	@Override
	public List<Integer> getOpenHosts(){
		return OpenHosts;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getTotalCloudCPULoad()
	 */
	@Override
	public int getTotalCloudCPULoad() {
		return totalCloudCPULoad;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getTotalCloudCpuDemand()
	 */
	@Override
	public double getTotalCloudCpuDemand() {
		return totalCloudCpuDemand;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getTotalCloudMemDemand()
	 */
	@Override
	public int getTotalCloudMemDemand() {
		return totalCloudMemDemand;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getTurnedONHosts()
	 */
	@Override
	public List<Host> getTurnedONHosts() {
		return turendONHosts;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getTurnedOFFHosts()
	 */
	@Override
	public List<Host> getTurnedOFFHosts(){
		return turendOFFHosts;
	}


	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getNumberOfOpenHosts()
	 */
	@Override
	public int getNumberOfOpenHosts() {
		return numberOfOpenHosts;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getNumberOfClosedHosts()
	 */
	@Override
	public int getNumberOfClosedHosts() {
		return numberOfCloseHosts;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getNumberOfCandidatesForClosingHosts()
	 */
	@Override
	public int getNumberOfCandidatesForClosingHosts() {
		return numberOfCandidateHosts;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getNumberOfTurnOffHosts()
	 */
	@Override
	public int getNumberOfTurnOffHosts() {
		return turendOFFHosts.size();
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getNumberOfTurnOnHosts()
	 */
	@Override
	public int getNumberOfTurnOnHosts() {
		return turendONHosts.size();
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getMinCPULoadInHosts()
	 */
	@Override
	public double getMinCPULoadInHosts() {
		return minCpuLoadInHosts;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getMinMEMLoadInHosts()
	 */
	@Override
	public double getMinMEMLoadInHosts() {
		return minMemLoadInHosts;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getAvgCPULoadInHosts()
	 */
	@Override
	public double getAvgCPULoadInHosts() {
		return (totalCloudCPULoad / hostManager.getNumberOfHosts());
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getAvgMEMLoadInHosts()
	 */
	@Override
	public double getAvgMEMLoadInHosts() {
		return (totalCloudMemDemand / hostManager.getNumberOfHosts());
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getMaxCPULoadInHosts()
	 */
	@Override
	public double getMaxCPULoadInHosts() {
		return maxCpuLoadInHosts;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getMaxMEMLoadInHosts()
	 */
	@Override
	public double getMaxMEMLoadInHosts() {
		return maxMemLoadInHosts;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getVMSrequestedTobedeployed()
	 */
	@Override
	public List<VirtualMachine> getVMSrequestedTobedeployed() {
		return VMSrequestedTobedeployed;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getVMSWithSatisfiedDemand()
	 */
	@Override
	public List<VirtualMachine> getVMSWithSatisfiedDemand() {
		return vmsWithSatisfiedDemand;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getVMSWithUNSatisfiedDemand()
	 */
	@Override
	public List<VirtualMachine> getVMSWithUNSatisfiedDemand() {
		return vmsWithUNSatisfiedDemand;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getRejectedVMS()
	 */
	@Override
	public List<VirtualMachine> getRejectedVMS() {
		return RejectedVMS;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getRemovedList()
	 */
	@Override
	public List<VirtualMachine> getRemovedList() {
		return removedList;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getPlacedList()
	 */
	@Override
	public List<VirtualMachine> getPlacedList() {
		return PlacedVMS;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getNumberOfVMsAtCloud()
	 */
	@Override
	public int getNumberOfVMsAtCloud() {
		return numberOfVMSinCloud;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getNumberOfVMsWithSatisfiedDemand()
	 */
	@Override
	public int getNumberOfVMsWithSatisfiedDemand() {
		return vmsWithSatisfiedDemand.size();
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getNumberOfVMsWithUnSatisfiedDemand()
	 */
	@Override
	public int getNumberOfVMsWithUnSatisfiedDemand() {
		return vmsWithUNSatisfiedDemand.size();
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getTotalSatisfiedCPUDemand()
	 */
	@Override
	public int getTotalSatisfiedCPUDemand() {
		return totalSatisfiedDemand;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getTotalUnSatisfiedCPUDemand()
	 */
	@Override
	public int getTotalUnSatisfiedCPUDemand() {
		return totalUNSatisfiedDemand;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getNumberOfRemovedVMs()
	 */
	@Override
	public int getNumberOfRemovedVMs() {
		return removedList.size();
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getNumberOfPlacedVMs()
	 */
	@Override
	public int getNumberOfPlacedVMs() {
		return PlacedVMS.size();
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#isEmptyPlacement()
	 */
	@Override
	public boolean isEmptyPlacement() {
		return emptyPlacement;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getvmMapping()
	 */
	@Override
	public Map<Host, List<VirtualMachine>> getvmMapping(){
		return this.vmMapping;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getCloudMap()
	 */
	@Override
	public Map<Integer, List<VMIdentifier>> getCloudMap(){
		return this.CloudMap;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#isHostOpen(eval.host.Host)
	 */
	@Override
	public boolean isHostOpen(Host host){
		for(Host iterHost : vmMapping.keySet()){
			if(iterHost.getHostId() == host.getHostId() && iterHost.isOpen())
				return true;
		}
		return false;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#isHostClose(eval.host.Host)
	 */
	@Override
	public boolean isHostClose(Host host){
		for(Host iterHost : getvmMapping().keySet()){
			if(iterHost.getHostId() == host.getHostId() && iterHost.isClose())
				return true;
		}
		return false;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getHostFromLastPlacement(eval.host.Host)
	 */
	@Override
	public Host getHostFromLastPlacement(Host host){
		for(Host iterHost : lastPlacement.getvmMapping().keySet()){
			if(iterHost.getHostId() == host.getHostId() )
				return iterHost;
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getNumberOfVmsReqToBeDeploy()
	 */
	@Override
	public int getNumberOfVmsReqToBeDeploy() {
		return getVMSrequestedTobedeployed().size();
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getTotalCpuCapcityAtOpenHosts()
	 */
	@Override
	public int getTotalCpuCapcityAtOpenHosts() {
		return totalCpuCapcityAtOpenHosts;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getTotalCpuCapcityAtCloud()
	 */
	@Override
	public int getTotalCpuCapcityAtCloud() {
		return totalCpuCapcityAtCloud;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getTotalMemCapcityAtOpenHosts()
	 */
	@Override
	public int getTotalMemCapcityAtOpenHosts() {
		return totalMemCapcityAtOpenHosts;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getTotalCloudMemAtCloud()
	 */
	@Override
	public int getTotalCloudMemAtCloud() {
		return totalCloudMemAtCloud;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getBaseHostCost()
	 */
	@Override
	public double getBaseHostCost() {
		return baseHostCost;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getHostsStartUpCost()
	 */
	@Override
	public double getHostsStartUpCost() {
		return hostsStartUpCost;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getWorkingHostCost()
	 */
	@Override
	public double getWorkingHostCost() {
		return hostManager.getLoadCost(totalCloudCPULoad);
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getIncomeForSatisfyDemand()
	 */
	@Override
	public double getIncomeForSatisfyDemand() {
		return hostManager.getIncomeOnLoad(getTotalCloudCPULoad());
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getTotalMemLoadAtCloud()
	 */
	@Override
	public int getTotalMemLoadAtCloud() {
		return hostManager.getToatalMemLoadOnCloud();
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getSumOfMinReservtion()
	 */
	@Override
	public double getSumOfMinReservtion() {
		return  sumOfMinREservtion;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getCplexMapIndexToVmId()
	 */
	@Override
	public CplexMapIndexToVmId getCplexMapIndexToVmId() {
		return null;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getCplexData()
	 */
	@Override
	public CplexDataHolder getCplexData() {
		return null;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getVmRelocate()
	 */
	@Override
	public int getVmRelocate() {
		return vmRelocate;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getTotalMemCapcityAtCloud()
	 */
	@Override
	public int getTotalMemCapcityAtCloud() {
		return totalCloudMemAtCloud;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getFreeCpuAtHosts()
	 */
	@Override
	public double[] getFreeCpuAtHosts() {
		return freeCpuAtHosts;
	}

	/* (non-Javadoc)
	 * @see eval.engine.IPlacement#getGradientValue()
	 */
	@Override
	public int getGradientValue() {
		return gradient;
	}



}